﻿'    This file is part of The Global Distribution System.

'    The Global Distribution System is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 3 of the License, or
'    (at your option) any later version.

'    The Global Distribution System is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with The Global Distribution System.  If not, see <http://www.gnu.org/licenses/>.

Option Strict On

Imports System.Runtime.Serialization, System.Runtime.Serialization.Formatters
Imports System.Net, System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports System.Text, System.Text.RegularExpressions

''' <summary>
''' The Public GDS Class.
''' </summary>
''' <remarks></remarks>
Public Class GDS
    Implements IDisposable

#Region " Variables and Properties "

    Private WithEvents ClientHandler As New ClientHandler
    Private WithEvents ReportHandler As New ReportHandler
    Private SearchHandler As New SearchHandler
    Private ReservationHandler As New ReservationHandler
    Public BanHandler As New IPBanHandler

    Private AcceptingThread As New System.Threading.Thread(AddressOf ListeningThread) With {.IsBackground = True}
    Private IncomingClient As TcpClient
    Private Listener As TcpListener
    Private _enableUploads As Boolean = True

    Private disposed As Boolean = False

    ''' <summary>
    ''' Returns the Internal Number of the GTS.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Version As Version
        Get
            Return New Version(1I, 5I, 148I, 5I)
        End Get
    End Property

    ''' <summary>
    ''' Returns the ClientHandler for the User Manager to use.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GetClientHandler As ClientHandler
        Get
            Return ClientHandler
        End Get
    End Property

    ''' <summary>
    ''' Enables or Disables uploading on the Global Distribution System.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property EnableUploads As Boolean
        Get
            Return _enableUploads
        End Get
        Set(ByVal value As Boolean)
            _enableUploads = value
            Me.Log(String.Format("Uploads are now {0}", If(_enableUploads, "enabled", "disabled")))
        End Set
    End Property

    ''' <summary>
    ''' A General Logging Event.
    ''' </summary>
    ''' <param name="message">The message that should be logged.</param>
    ''' <remarks></remarks>
    Public Event GeneralLog(ByVal message As String)

#End Region

#Region " Subs and Functions "

    ''' <summary>
    ''' Initializes the Global Distribution System.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        Listener = New TcpListener(Net.IPAddress.Any, 666I)
    End Sub

    ''' <summary>
    ''' Starts the GDS System.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub [Start]()
        Listener.Start()
        AcceptingThread.Start()
    End Sub

    ''' <summary>
    ''' Stops the GDS System.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub [Stop]()
        Try
            AcceptingThread.Abort()
            ClientHandler.Dispose()
            AcceptingThread = New System.Threading.Thread(AddressOf ListeningThread) With {.IsBackground = True}
        Catch
        End Try
    End Sub

    ''' <summary>
    ''' Returns a List of all online/connected Clients.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnClients() As List(Of String)
        Return ClientHandler.ReturnIPList
    End Function

    ''' <summary>
    ''' Shows the Options.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ShowOptions()
        Dim FO As New Form_Options(Me)
        FO.Show()
    End Sub

    ''' <summary>
    ''' Shows the Client Manager.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ShowClientManager()
        Dim CM As New Form_ClientManager(Me.ClientHandler)
        CM.Show()
    End Sub
    
    ''' <summary>
    ''' Creates TextFiles for the .PKM files in the \PokemonFiles\ Directory.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub CreateTextFiles()
        Me.Log("Creating data files for the Pokemon in the \PokemonFiles\ Directory")

        Dim PkmHandler As New PokemonHandler(SearchHandler)

        Directory.Delete(SearchHandler.TextFiles, True)
        Directory.CreateDirectory(SearchHandler.TextFiles)
        For Each item As String In Directory.GetFiles(SearchHandler.PokemonFiles, "*.pkm")
            Me.Log(String.Format("Creating data for {0}.", Path.GetFileName(item)))
            Dim Info As New DLL.PokemonData
            Info.PokemonName = Path.GetFileNameWithoutExtension(item)
            Info.IP = "127.0.0.1"
            Info.ID = PkmHandler.CalculateID(item)
            Info.PokemonData = File.ReadAllBytes(item)
            Call PkmHandler.CreateNewTextFile(item, Info)
        Next
        SearchHandler.Refresh()
    End Sub

    ''' <summary>
    ''' Forces the Searcher to Refresh.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub RefreshSearcher()
        SearchHandler.Refresh()
        Me.Log("The Search Engine was forcefully refreshed.")
    End Sub

    ''' <summary>
    ''' Returns the size of the search list.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub GetSearchSize()
        Me.Log(String.Format("There are {0} Pokemon in the Search Engine.", SearchHandler.ReturnCount))
    End Sub

    ''' <summary>
    ''' Endless Listener thread.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub ListeningThread()
        Try
            Do
                Me.Log("Waiting for connection.")
                IncomingClient = Listener.AcceptTcpClient
                Me.Log("Connection established, adding handlers.")
                ClientHandler.AcceptClient(IncomingClient)
            Loop
        Catch ex As Exception
            Me.Log("Error! Restart the GDS!")
            Me.Log(ex.Message)
            Me.Stop()
        End Try
    End Sub

    ''' <summary>
    ''' Raises the GeneralLog event with data.
    ''' </summary>
    ''' <param name="data">The data to log.</param>
    ''' <remarks></remarks>
    Private Sub Log(ByVal data As String)
        RaiseEvent GeneralLog(data)
    End Sub

#End Region

#Region " Client Handlers "

    Private Sub ClientHandler_ClientError(ByVal IP As String, ByVal ErrorData As System.Exception) Handles ClientHandler.ClientError
        RaiseEvent GeneralLog(String.Format("IP {0} has errored out: {1}{2}", IP, Environment.NewLine, ErrorData.ToString))
    End Sub

    Private Sub ClientHandler_ClientMessage(ByVal IP As String, ByVal data As Object) Handles ClientHandler.ClientMessage
        'Process a pokemon reservation.
        If data.GetType Is GetType(DLL.ReservePokemon) Then
            Me.Log(String.Format("{0} is trying to Reserve a Pokemon", IP))
            'Convert Structure
            Dim RP As DLL.ReservePokemon = CType(data, DLL.ReservePokemon)
            'Check for existing reservation
            If ReservationHandler.CheckForReservation(IP) AndAlso Not RP.Overwrite Then
                'Reservation exists and no override protection.
                Me.Log(String.Format("{0} has an existing reservation, rejected.", IP))
                ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = CInt(GlobalDistributionServer.ClientHandler.Message.ReservationRejected).ToString})
            Else
                Me.Log(String.Format("{0} has no existing reservation, creating.", IP))
                'We're clear to make the reservation OR override is enabled.
                'Just to make sure, call the removal.
                Call ReservationHandler.RemoveReservation(IP)
                'Now create the reservation.
                If ReservationHandler.CreateReservation(IP, RP.ID) Then
                    ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = CInt(GlobalDistributionServer.ClientHandler.Message.ReservationAccepted).ToString})
                Else
                    ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = CInt(GlobalDistributionServer.ClientHandler.Message.ReservationRejected).ToString})
                End If
            End If
        End If

        'Custom Uploaded Pokemon
        If data.GetType Is GetType(DLL.PokemonData) Then
            Me.Log(String.Format("{0} is uploading a custom Pokemon.", IP))
            Dim PD As DLL.PokemonData = CType(data, DLL.PokemonData)
            PD.IP = IP
            Dim PokemonHandler As New PokemonHandler(SearchHandler)
            If EnableUploads Then
                If Not PokemonHandler.CheckForExistingPokemon(PD.ID) Then
                    Select Case PokemonHandler.CreateNewPokemon(PD)
                        Case True
                            Me.Log(String.Format("{0}'s Pokemon ({1}) has been accepted as unique! ID: {2}", IP, PD.PokemonName, PD.ID))
                            ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = CInt(GlobalDistributionServer.ClientHandler.Message.UploadSuccessful).ToString})
                            SearchHandler.Refresh()
                        Case False
                            Me.Log(String.Format("{0}'s Pokemon ({1}) has been rejected! ID: {2}", IP, PD.PokemonName, PD.ID))
                            ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = CInt(GlobalDistributionServer.ClientHandler.Message.UploadFailed).ToString})
                    End Select
                Else
                    Me.Log(String.Format("{0}'s Pokemon ({1}) has been rejected! ID: {2}", IP, PD.PokemonName, PD.ID))
                    ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = CInt(GlobalDistributionServer.ClientHandler.Message.UploadFailed).ToString})
                End If
            Else
                Me.Log(String.Format("{0}'s Pokemon ({1}) has been rejected! ID: {2}", IP, PD.PokemonName, PD.ID))
                ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = CInt(GlobalDistributionServer.ClientHandler.Message.UploadFailed).ToString})
            End If

        End If

        'Search Request
        If data.GetType Is GetType(DLL.SearchRequest) Then
            Me.Log(String.Format("{0} is requesting a search.", IP))
            Dim RD As DLL.SearchRequest = CType(data, DLL.SearchRequest)
            Dim SR As New DLL.SearchResults

            Select Case RD.Search
                Case True
                    Me.Log(String.Format("{0} has searched for Pokemon containing '{1}'.", IP, RD.Name))
                    SR.Search = True
                    SR.Results = SearchHandler.ReturnFilteredList(RD.Name.ToLower)
                Case False
                    SR.Search = False
                    Me.Log(String.Format("{0} has requested an entire list.", IP))
                    SR.Results = SearchHandler.ReturnUnfilteredList()
            End Select

            ClientHandler.SendSpecificMessage(IP, SR)
        End If

        'Ping check.
        If data.GetType Is GetType(DLL.MessageStructure) Then
            Dim MS As DLL.MessageStructure = CType(data, DLL.MessageStructure)
            If MS.Message = "0" Then ClientHandler.SendSpecificMessage(IP, New DLL.MessageStructure With {.Message = "5"})
            Me.Log(String.Format("Ping check from {0}", IP))
        End If

        'Information Request.
        If data.GetType Is GetType(DLL.RequestPokemonInformation) Then
            Dim RPI As DLL.RequestPokemonInformation = CType(data, DLL.RequestPokemonInformation)
            Select Case RPI.KeyOption
                Case DLL.Options.Information
                    Me.Log(String.Format("{0} is requesting more information on {1}", IP, RPI.ID))
                    Dim TextFilePath As New PokemonTextReader(SearchHandler.ReturnFilePath(RPI.ID))
                    Dim PkmReader As New PokemonReader(TextFilePath.Path)
                    Dim PokeInfo As DLL.Information = PkmReader.ReturnPokemonStructure
                    PokeInfo.InternalID = RPI.ID
                    ClientHandler.SendSpecificMessage(IP, PokeInfo)
                Case DLL.Options.Download
                    Me.Log(String.Format("{0} wants to download a Pokemon with the ID: {1}", IP, RPI.ID))
                    Dim PkmStruct As New DLL.PokemonData
                    Dim TPF As New PokemonTextReader(SearchHandler.ReturnFilePath(RPI.ID))
                    Dim bytes As Byte() = File.ReadAllBytes(TPF.Path)
                    PkmStruct.PokemonData = bytes
                    PkmStruct.ID = RPI.ID
                    PkmStruct.PokemonName = TPF.Name
                    ClientHandler.SendSpecificMessage(IP, PkmStruct)
            End Select

        End If

        'Chat Message
        If data.GetType Is GetType(DLL.ChatMessage) Then
            Me.Log(String.Format("Chat Message from {0}", IP))
            ClientHandler.SendChatMessage(data)
        End If

    End Sub

    Private Sub ClientHandler_Logging(ByVal log As String) Handles ClientHandler.Logging
        RaiseEvent GeneralLog(log)
    End Sub

#End Region

#Region " Disposing "

    ''' <summary>
    ''' Disposes of the Global Distribution System and the underlying classes.
    ''' </summary>
    ''' <remarks></remarks>
    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Me.Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ''' <summary>
    ''' Disposes of the Global Distrubtion System and all attached items.
    ''' </summary>
    ''' <param name="disposing">When True, automatically disposes of the rest of the sub classes.</param>
    ''' <remarks></remarks>
    Private Overloads Sub Dispose(ByVal disposing As Boolean)
        ' Check to see if Dispose has already been called.
        If Not Me.disposed Then
            'Clear local variables only.
            Try
                'Remove IncomingClient.
                If IncomingClient IsNot Nothing Then IncomingClient.Client.Close()
                'Abort Thread.
                AcceptingThread.Abort()
            Catch ex As System.Threading.ThreadAbortException
            Catch ex As Exception
            Finally
                'Stop Listening.
                Listener.Stop()
            End Try
            If disposing Then
                'If true, get rid of all the damn subclasses.
                ClientHandler.Dispose()
                'ReportHandler.Dispose()
                'SearchHandler.Dispose()
                'ReservationHandler.Dispose()
                BanHandler.Dispose()
            End If
        End If
        disposed = True
    End Sub

    ''' <summary>
    ''' Disposes of the Global Distribution System but NOT the underlying classes.
    ''' </summary>
    ''' <remarks></remarks>
    Protected Overrides Sub Finalize()
        ' Do not re-create Dispose clean-up code here.
        ' Calling Dispose(false) is optimal in terms of
        ' readability and maintainability.
        Dispose(False)
        MyBase.Finalize()
    End Sub

#End Region

End Class

''' <summary>
''' This class handles the reservations.
''' </summary>
''' <remarks></remarks>
Public Class ReservationHandler


    ''' <summary>
    ''' Returns the executing location of this assembly.
    ''' This is safe to do because this DLL has to be where
    ''' HyperGTS is, which would automatically place it in
    ''' the right area for this whole system to work.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property AssemblyPath As String
        Get
            Return IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.Location)
        End Get
    End Property

    ''' <summary>
    ''' Returns the location of the user request files.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property RequestFilePath As String
        Get
            Return Path.Combine(AssemblyPath, "UserRequests")
        End Get
    End Property

    ''' <summary>
    ''' Checks to see if a Client has reserved a pokemon.
    ''' </summary>
    ''' <param name="IP">The IP to check for.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CheckForReservation(ByVal IP As String) As Boolean
        For Each item As String In Directory.GetFiles(RequestFilePath, "*.txt")
            If Path.GetFileNameWithoutExtension(item) = IP Then Return True
        Next
        Return False
    End Function

    ''' <summary>
    ''' Returns the Reservation File based on the IP address.
    ''' </summary>
    ''' <param name="IP">The IP to look for.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnReservationFile(ByVal IP As String) As String
        For Each item As String In Directory.GetFiles(RequestFilePath, "*.txt")
            If Path.GetFileNameWithoutExtension(item) = IP Then Return item
        Next
        Return String.Empty
    End Function

    ''' <summary>
    ''' Creates a reservation for a client -if- they don't have one already.
    ''' </summary>
    ''' <param name="IP">The IP to make the reservation for.</param>
    ''' <param name="ID">The ID that the client has reserved.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CreateReservation(ByVal IP As String, ByVal ID As String) As Boolean
        If Not CheckForReservation(IP) Then
            Using SW As New StreamWriter(Path.Combine(RequestFilePath, String.Format("{0}.txt", IP)))
                SW.WriteLine(ID)
            End Using
            Return True
        Else
            Return False
        End If
    End Function

    ''' <summary>
    ''' Removes a Reservation from the system.
    ''' </summary>
    ''' <param name="IP">The IP.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function RemoveReservation(ByVal IP As String) As Boolean
        If CheckForReservation(IP) Then
            'We do have a reservation, remove it.
            Dim FP As String = Path.Combine(RequestFilePath, String.Format("{0}.txt", IP))
            If File.Exists(FP) Then File.Delete(FP) : Return True Else Return False
        Else
            Return False
        End If
    End Function

End Class

''' <summary>
''' This class handles the Ban List.
''' </summary>
''' <remarks></remarks>
Public Class IPBanHandler

    ''' <summary>
    ''' Returns the executing location of this assembly.
    ''' This is safe to do because this DLL has to be where
    ''' HyperGTS is, which would automatically place it in
    ''' the right area for this whole system to work.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property AssemblyPath As String
        Get
            If Me.Disposed Then Throw New ObjectDisposedException("IPBanHandler", "This Class has been disposed of!")
            Return IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.Location)
        End Get
    End Property

    ''' <summary>
    ''' The path to the ban list.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property BanFile As String
        Get
            If Me.Disposed Then Throw New ObjectDisposedException("IPBanHandler", "This Class has been disposed of!")
            Return Path.Combine(AssemblyPath, "banlist.txt")
        End Get
    End Property

    ''' <summary>
    ''' The List of Banned IPs
    ''' </summary>
    ''' <remarks></remarks>
    Private IPList As New List(Of String)

    Private Disposed As Boolean = False

    ''' <summary>
    ''' Initializes the Ban Handler.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        'Check for banlist.txt
        If File.Exists(BanFile) Then
            'If found, load it in.
            IPList.AddRange(File.ReadAllLines(BanFile))
        Else
            'Not found, create it.
            Using File.CreateText(BanFile)
            End Using
        End If
    End Sub

    ''' <summary>
    ''' Disposes of the BanHandler.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Dispose()
        If File.Exists(BanFile) Then File.Delete(BanFile)
        Using SW As New StreamWriter(BanFile)
            For Each item As String In IPList
                SW.WriteLine(item)
            Next
        End Using
        Me.Disposed = True
    End Sub

    ''' <summary>
    ''' Checks to see if 
    ''' </summary>
    ''' <param name="IP">The IP Address to check</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function IsIPBanned(ByVal IP As String) As Boolean
        If Me.Disposed Then Throw New ObjectDisposedException("IPBanHandler", "This Class has been disposed of!")
        'Make sure it's a valid IP address.
        If IsIPv4(IP) Then
            SyncLock IPList
                For CurrentIP As Integer = IPList.Count - 1 To 0 Step -1
                    If IPList(CurrentIP) = IP Then Return True
                Next
            End SyncLock
        End If
        Return False
    End Function

    ''' <summary>
    ''' Bans an IP if it's not already on the ban list.
    ''' </summary>
    ''' <param name="IP">The IP Address to ban.</param>
    ''' <remarks></remarks>
    Public Sub BanIP(ByVal IP As String)
        If Me.Disposed Then Throw New ObjectDisposedException("IPBanHandler", "This Class has been disposed of!")
        'If it's not banned and if it's a valid IP
        If Not IsIPBanned(IP) Then
            SyncLock IPList
                'BANHAMMER
                IPList.Add(IP)
            End SyncLock
        End If
    End Sub

    ''' <summary>
    ''' Unbans an IP if it's on the ban list.
    ''' </summary>
    ''' <param name="IP">The IP to unban</param>
    ''' <remarks></remarks>
    Public Sub UnBanIP(ByVal IP As String)
        If Me.Disposed Then Throw New ObjectDisposedException("IPBanHandler", "This Class has been disposed of!")
        'If it's banned and also if it's a valid IP
        If IsIPBanned(IP) Then
            SyncLock IPList
                For i As Integer = IPList.Count - 1 To 0 Step -1
                    If IPList(i) = IP Then IPList.RemoveAt(i)
                Next
            End SyncLock
        End If
    End Sub

    ''' <summary>
    ''' Checks to see if the passed string is a valid IP address.
    ''' </summary>
    ''' <param name="ip">The string to check.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function IsIPv4(ByVal ip As String) As Boolean
        Return New Regex("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$").IsMatch(ip)
    End Function

End Class

''' <summary>
''' Handles all reports filed on blatantly hacked Pokemon.
''' </summary>
''' <remarks></remarks>
Public Class ReportHandler

    Private ReportList As New List(Of Report)

    Public Structure Report
        Public Property UploaderIP As String
        Public Property ReporterIP As String
        Public Property ID As String
        Public Overrides Function ToString() As String
            Return Me.ID
        End Function
        Public Overrides Function Equals(ByVal obj As Object) As Boolean
            If obj.GetType Is GetType(Report) Then
                Return Me.ID.Equals(CType(obj, Report).ID)
            Else
                Return False
            End If
        End Function
    End Structure

    ''' <summary>
    ''' Adds a Report if one does not exist already.
    ''' </summary>
    ''' <param name="report">The report to add.</param>
    ''' <remarks></remarks>
    Public Sub [Add](ByVal report As Report)
        If Not ReportList.Contains(report) Then ReportList.Add(report)
    End Sub

    Public Sub [Add](ByVal UIP As String, ByVal RIP As String, ByVal ID As String)
        Me.Add(New Report With {.ID = ID, .ReporterIP = RIP, .UploaderIP = UIP})
    End Sub

    Public Sub Remove(ByVal report As Report)
        If ReportList.Contains(report) Then ReportList.Remove(report)
    End Sub

    Public Sub Remove(ByVal ID As String)
        SyncLock ReportList
            For i As Integer = ReportList.Count - 1 To 0 Step -1
                If ReportList(i).ID.Equals(ID) Then ReportList.RemoveAt(i) : Return
            Next
        End SyncLock
    End Sub

End Class

''' <summary>
''' This class handles all the Clients.
''' </summary>
''' <remarks></remarks>
Public Class ClientHandler

    ''' <summary>
    ''' Used when the Client list needs to be passed around.
    ''' </summary>
    ''' <remarks></remarks>
    Public Structure StoredClient
        Public Property IP As String
        Public Property ChatName As String
    End Structure

    ''' <summary>
    ''' The message Enum handles what specific message to send back to the client.
    ''' </summary>
    ''' <remarks></remarks>
    Public Enum Message As Integer

        ''' <summary>
        ''' Sent when reserving a Pokemon is a success.
        ''' </summary>
        ''' <remarks></remarks>
        ReservationAccepted = 0

        ''' <summary>
        ''' Send when reserving a Pokemon is rejected.
        ''' </summary>
        ''' <remarks></remarks>
        ReservationRejected = 1

        ''' <summary>
        ''' Send when uploading a custom .PKM is successful.
        ''' </summary>
        ''' <remarks></remarks>
        UploadSuccessful = 2

        ''' <summary>
        ''' Send when uploading a custom .PKM failed.
        ''' </summary>
        ''' <remarks></remarks>
        UploadFailed = 3

    End Enum

    ''' <summary>
    ''' Structure for using the threaded message sub.
    ''' </summary>
    ''' <remarks></remarks>
    Private Structure ThreadedMessageData
        Public Property IP As String
        Public Property Data As Object
    End Structure

    ''' <summary>
    ''' This is the Client List. It holds every single active client on the GDS.
    ''' </summary>
    ''' <remarks></remarks>
    Private ClientList As New List(Of Client)

    ''' <summary>
    ''' A Giant List(Of String) that holds every logging action the ClientHandler does.
    ''' </summary>
    ''' <remarks></remarks>
    Private LogData As New List(Of String)

    ''' <summary>
    ''' Raised when a Client joins the system.
    ''' </summary>
    ''' <param name="IP">The IP address of the client.</param>
    ''' <remarks></remarks>
    Public Event ClientLogin(ByVal IP As String)

    ''' <summary>
    ''' Raised when a Client joins the chatroom.
    ''' </summary>
    ''' <param name="IP">The IP address of the client.</param>
    ''' <param name="name">The name used in the chatroom.</param>
    ''' <remarks></remarks>
    Public Event ClientChatJoin(ByVal IP As String, ByVal name As String)

    ''' <summary>
    ''' Raised when a Client leaves the chatroom.
    ''' </summary>
    ''' <param name="IP">The IP address of the client.</param>
    ''' <remarks></remarks>
    Public Event ClientLeavesChat(ByVal IP As String)

    ''' <summary>
    ''' This Event is raised whenever the Client receives some new data that the Global Distribution System should be aware of.
    ''' </summary>
    ''' <param name="IP">The IP address of the sender.</param>
    ''' <param name="data">The data that the sender sent.</param>
    ''' <remarks></remarks>
    Public Event ClientMessage(ByVal IP As String, ByVal data As Object)

    ''' <summary>
    ''' This Event is raised whenever the Client errors out. The IP address and the Error Data are sent.
    ''' </summary>
    ''' <param name="IP">The IP address of the client that errored out.</param>
    ''' <param name="ErrorData">The Exception data.</param>
    ''' <remarks></remarks>
    Public Event ClientError(ByVal IP As String, ByVal ErrorData As Exception)

    ''' <summary>
    ''' This event is raised whenever data is added to the logger.
    ''' </summary>
    ''' <param name="log">The message added to the log.</param>
    ''' <remarks></remarks>
    Public Event Logging(ByVal log As String)


    ''' <summary>
    ''' The Global Distribution Client Sub Class. This is not directly exposed to the rest of the system.
    ''' </summary>
    ''' <remarks></remarks>
    Private Class Client
        Implements IEquatable(Of Client)

        ''' <summary>
        ''' IP address
        ''' </summary>
        ''' <remarks></remarks>
        Private PublicIP As String = String.Empty

        ''' <summary>
        ''' The infinite reading thread.
        ''' </summary>
        ''' <remarks></remarks>
        Private readThread As New Threading.Thread(AddressOf client_doRead) With {.IsBackground = True}

        ''' <summary>
        ''' The Connected Client.
        ''' </summary>
        ''' <remarks></remarks>
        Private m_Client As TcpClient 'the tcp client, do not ever dispose this!

        ''' <summary>
        ''' Formatter for Handling queries.
        ''' </summary>
        ''' <remarks></remarks>
        Private BinFormatter As Binary.BinaryFormatter 'The binary formatter that handles our queries.

        ''' <summary>
        ''' Our Timer for checking for timeouts.
        ''' </summary>
        ''' <remarks></remarks>
        Private WithEvents Timer As New System.Timers.Timer With {.AutoReset = True, .Interval = 9000, .Enabled = False}

        ''' <summary>
        ''' Username, used for the chatting.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Username As String = String.Empty

        ''' <summary>
        ''' Basically says if they're logged in or not.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Loggedin As Boolean
            Get
                Return Username <> String.Empty
            End Get
        End Property

        ''' <summary>
        ''' The Message Received Event.
        ''' </summary>
        ''' <param name="cli">The Client that sent the message.</param>
        ''' <param name="obj">The Object the client sent.</param>
        ''' <remarks></remarks>
        Friend Event Message(ByVal cli As Client, ByVal obj As Object)

        ''' <summary>
        ''' The Error Event for a Client.
        ''' </summary>
        ''' <param name="cli">The Client that threw the error.</param>
        ''' <param name="err">The exception data.</param>
        ''' <remarks></remarks>
        Friend Event ErrorOut(ByVal cli As Client, ByVal err As Exception)

        ''' <summary>
        ''' The event for when this client logs in and out of the chatroom.
        ''' </summary>
        ''' <param name="cli">The client.</param>
        ''' <param name="data">The info.</param>
        ''' <remarks></remarks>
        Friend Event ChatInformation(ByVal cli As Client, ByVal data As DLL.IndividualPerson)

        ''' <summary>
        ''' Create a new Client.
        ''' </summary>
        ''' <param name="client">The TCP Client that's connecting.</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal client As TcpClient)
            m_Client = client 'Transfer over the TcpClient to this class.
            m_Client.SendBufferSize = 1400 'Set the byte size of the write/send buffer
            m_Client.ReceiveBufferSize = 1400 'Set the byte size of the read/receive buffer
            readThread.Start() 'Start our infinite reading thread.
            Timer.Enabled = True 'create our timeout timer.
            Timer.Start() 'Start the timer.
        End Sub

        ''' <summary>
        ''' The infinite doRead() sub. Handles all reading from the client.
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub client_doRead()
            Try
                Do
                    'Deserialize the object coming in.
                    Dim obj As Object = New Binary.BinaryFormatter().Deserialize(m_Client.GetStream)
                    'Do a chat test.
                    If obj.GetType Is GetType(DLL.IndividualPerson) Then
                        'Well, let's see
                        Dim ChatInfo As DLL.IndividualPerson = CType(obj, DLL.IndividualPerson)
                        Select Case ChatInfo.login
                            Case True
                                'Logging in!
                                If Not Me.Loggedin Then Me.Username = ChatInfo.person : RaiseEvent ChatInformation(Me, ChatInfo)
                            Case False
                                'Logging out.
                                RaiseEvent ChatInformation(Me, New DLL.IndividualPerson With {.person = Me.Username, .login = False})
                                Me.Username = String.Empty
                        End Select
                    Else
                        'Pass it in the Message Event.
                        RaiseEvent Message(Me, obj)
                    End If
                Loop
            Catch ex As Threading.ThreadAbortException
                'The thread aborted, no real need to do much about it.
                Debug.WriteLine("Threading Exception, probably aborted.")
            Catch ex As SerializationException
                'Serialization error. This could mean an illegal object or the stream
                'got damaged/terminated.
                Debug.WriteLine("Serialization Error, that's not good.")
                Debug.WriteLine(ex.ToString)
                RaiseEvent ErrorOut(Me, ex)
                Me.Dispose()
            Catch ex As IOException
                'An IO Exception could mean quite a few things(stream dying comes to
                'mind as well), but overall, it's an error that should be handled.
                Debug.WriteLine("IO Exception. Stream died.")
                RaiseEvent ErrorOut(Me, ex)
                Me.Dispose()
            Catch ex As Exception
                'An exception that I didn't think about, woo.
                Debug.WriteLine("General Exception")
                RaiseEvent ErrorOut(Me, ex)
                Me.Dispose()
            End Try
        End Sub

        ''' <summary>
        ''' Send a Serialized Message to the Client.
        ''' </summary>
        ''' <param name="message">The Object to send.</param>
        ''' <remarks></remarks>
        Public Sub SendMessage(ByVal message As Object)
            'Since I'm going to multi-thread this, we're going to just literally create a thread dedicated
            'to sending the message.
            Dim SMAT As New Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf SendMessageAsync))
            SMAT.Start(message)
        End Sub

        ''' <summary>
        ''' Sends a message across the stream Asynchronously.
        ''' </summary>
        ''' <param name="message">The message to send.</param>
        ''' <remarks></remarks>
        Private Sub SendMessageAsync(ByVal message As Object)
            Try
                'Lock the stream.
                SyncLock m_Client.GetStream
                    'Create a new Formatter.
                    Dim BinSender As New Binary.BinaryFormatter
                    'Send the message.
                    BinSender.Serialize(m_Client.GetStream, message)
                End SyncLock
                'Reset the timeout timer.
                Timer.Stop()
                Timer.Start()
            Catch BSE As SerializationException
                'Binary Serialization Exception, should be noted.
                RaiseEvent ErrorOut(Me, BSE)
                Debug.WriteLine(BSE.ToString)
                Me.Dispose()
            Catch IOE As InvalidOperationException
                'IOE exception, uncommon, but should be noted.
                RaiseEvent ErrorOut(Me, IOE)
                Me.Dispose()
            Catch ex As Exception
                'General exception I didn't think to catch specifically.
                RaiseEvent ErrorOut(Me, ex)
                Me.Dispose()
            End Try
        End Sub

        ''' <summary>
        ''' Dispose of the Client
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub Dispose()
            Me.VariableReset()
        End Sub

        ''' <summary>
        ''' Resets a few variables to nothing.
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub VariableReset()
            Timer.Stop()
            Timer.Dispose()
            BinFormatter = Nothing
            Try
                readThread.Abort()
            Catch
            End Try
            m_Client.Close()
            m_Client.Client.Close()
            GC.Collect()
        End Sub

        ''' <summary>
        ''' Returns a String Representation of the Client.
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides Function ToString() As String
            Return Me.IP
        End Function

        ''' <summary>
        ''' Returns the IP address of the Client.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property IP() As String
            Get
                If PublicIP = String.Empty Then
                    Try
                        ' Get the clients IP address using Client property            

                        Dim ipend As Net.IPEndPoint = CType(m_Client.Client.RemoteEndPoint, Net.IPEndPoint)
                        If Not ipend Is Nothing Then
                            PublicIP = ipend.Address.ToString
                        End If
                    Catch ex As System.ObjectDisposedException
                        PublicIP = String.Empty
                    Catch ex As SocketException
                        PublicIP = String.Empty
                    End Try
                End If
                Return PublicIP
            End Get
        End Property

        Private Sub Timer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles Timer.Elapsed
            Me.SendMessage(New DLL.MessageStructure With {.Message = "5"})
        End Sub

        ''' <summary>
        ''' Checks to see if one Client is the same as another.
        ''' </summary>
        ''' <param name="other">The other client to check</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overloads Function Equals(ByVal other As Client) As Boolean Implements System.IEquatable(Of Client).Equals
            Return other.IP.Equals(Me.IP)
        End Function

        ''' <summary>
        ''' Checks to see if two clients are the same.
        ''' </summary>
        ''' <param name="one">Client One</param>
        ''' <param name="two">Client Two</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Operator =(ByVal one As Client, ByVal two As Client) As Boolean
            Return one.Equals(two)
        End Operator

        ''' <summary>
        ''' Check to see if two clients are not the same.
        ''' </summary>
        ''' <param name="one">Client One</param>
        ''' <param name="two">Client Two</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Operator <>(ByVal one As Client, ByVal two As Client) As Boolean
            Return Not one.Equals(two)
        End Operator

    End Class

    ''' <summary>
    ''' Disconnects all people from the Client Class.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Dispose()
        For i As Integer = ClientList.Count - 1 To 0 Step -1
            ClientList(i).Dispose()
        Next
    End Sub

    ''' <summary>
    ''' Generates a List(Of StoredClient) for organized access of the clients.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnOrganizedList() As List(Of StoredClient)
        Dim SC As New List(Of StoredClient)
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                With ClientList(i)
                    Dim SCI As New StoredClient
                    SCI.IP = .IP
                    SCI.ChatName = .Username
                    SC.Add(SCI)
                End With
            Next
        End SyncLock
        Return SC
    End Function

    ''' <summary>
    ''' Adds a new client ot the system (if the client doesn't already exist).
    ''' </summary>
    ''' <param name="Client">The TCP Client to 'accept'.</param>
    ''' <remarks></remarks>
    Public Sub AcceptClient(ByVal Client As TcpClient)
        Dim DummyClient As New Client(Client)
        Me.Log(String.Format("Connection from {0}", DummyClient.IP))
        If Not CheckForExistingClient(DummyClient.IP) Then
            Me.Log(String.Format("{0} isn't already connected, they are accepted.", DummyClient.IP))
            AddHandler DummyClient.Message, AddressOf MessageHandler
            AddHandler DummyClient.ErrorOut, AddressOf ErrorHandler
            AddHandler DummyClient.ChatInformation, AddressOf ClientChatHandler
            ClientList.Add(DummyClient)
            RaiseEvent ClientLogin(DummyClient.IP)
        Else
            Me.Log(String.Format("{0} already is connected, disconnect this one.", DummyClient.IP))
            DummyClient.Dispose()
        End If
    End Sub

    ''' <summary>
    ''' Checks for an existing Client based on IP address.
    ''' </summary>
    ''' <param name="IP"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CheckForExistingClient(ByVal IP As String) As Boolean
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                If ClientList(i).IP = IP Then Return True
            Next
        End SyncLock
        Return False
    End Function

    ''' <summary>
    ''' Returns a Client based on the IP address.
    ''' </summary>
    ''' <param name="IP">The IP to look for.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ReturnClient(ByVal IP As String) As Client
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                If ClientList(i).IP = IP Then Return ClientList(i)
            Next
        End SyncLock
        Return Nothing
    End Function

    ''' <summary>
    ''' Handles all messages from connected clients.
    ''' </summary>
    ''' <param name="sender">The sender.</param>
    ''' <param name="data">The message.</param>
    ''' <remarks></remarks>
    Private Sub MessageHandler(ByVal sender As Client, ByVal data As Object)
        RaiseEvent ClientMessage(sender.IP, data)
    End Sub

    ''' <summary>
    ''' Handles the exceptions from the clients and removes them from the client list.
    ''' </summary>
    ''' <param name="sender">The client that errored out.</param>
    ''' <param name="ExceptionData">EXCEPTION DATA, OH NO!</param>
    ''' <remarks></remarks>
    Private Sub ErrorHandler(ByVal sender As Client, ByVal ExceptionData As Exception)
        ClientList.Remove(sender)
        If sender.Loggedin Then
            RaiseEvent ClientLeavesChat(sender.IP)
            Dim User As String = sender.Username
            sender.Username = String.Empty
            'dispose of.
            sender.Dispose()
            'send a message
            Me.SendChatMessage(New DLL.IndividualPerson With {.person = User, .login = False})
            Me.SendChatMessage(New DLL.ChatMessage With {.sender = "SERVER", .message = String.Format("{0} has disconnected from the server!", User)})
        End If
        Me.Log(String.Format("{0} has disconnected.", sender.IP))
        RaiseEvent ClientError(sender.IP, ExceptionData)
    End Sub

    ''' <summary>
    ''' Handles when a client joins or leaves the chatroom.
    ''' </summary>
    ''' <param name="sender">The client that is joining/leaving.</param>
    ''' <param name="data">The Individual Person data.</param>
    ''' <remarks></remarks>
    Private Sub ClientChatHandler(ByVal sender As Client, ByVal data As DLL.IndividualPerson)
        Dim ChatData As New DLL.ChatMessage With {.sender = "SERVER"}
        Select Case data.login
            Case True
                'Someone has logged in.
                Me.Log(String.Format("{0} has joined the chatroom. (IP:{1})", data.person, sender.IP))
                ChatData.message = String.Format("{0} has joined the chatroom.", data.person)
                RaiseEvent ClientChatJoin(sender.IP, data.person)
            Case False
                'Someone logged out.
                sender.Username = String.Empty
                Me.Log(String.Format("{0} has left the chatroom (IP: {1})", data.person, sender.IP))
                ChatData.message = String.Format("{0} has left the chatroom.", data.person)
                RaiseEvent ClientLeavesChat(sender.IP)
        End Select
        'Send the data notifying someone has logged in.
        Me.SendChatMessage(ChatData)

        'Send the individual person data.
        Me.SendChatMessage(data)

        If sender.Loggedin Then
            'Now send the mass list to the person who just logged in.
            Dim MassList As New DLL.FullList With {.userlist = GetFullChatList()}
            sender.SendMessage(New DLL.IndividualPerson With {.login = False, .person = sender.Username})
            sender.SendMessage(MassList)
        End If


    End Sub

    ''' <summary>
    ''' Gets the full list of online users.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetFullChatList() As List(Of String)
        Dim ReturnList As New List(Of String)
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                If ClientList(i).Loggedin Then ReturnList.Add(ClientList(i).Username)
            Next
        End SyncLock
        Return ReturnList
    End Function

    ''' <summary>
    ''' Adds data to the log.
    ''' </summary>
    ''' <param name="data">LOGGING DATA!</param>
    ''' <remarks></remarks>
    Private Sub Log(ByVal data As String)
        LogData.Add(String.Format("{0}: {1}", DateTime.Now.ToShortTimeString, data))
        RaiseEvent Logging(data)
    End Sub

    ''' <summary>
    ''' Sends a specific message to the specified IP.
    ''' </summary>
    ''' <param name="IP">The IP address to send to.</param>
    ''' <param name="data">The data to send.</param>
    ''' <remarks></remarks>
    Public Sub SendSpecificMessage(ByVal IP As String, ByVal data As Object)
        Dim TMS As New System.Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf ThreadedSpecificMessage)) With {.IsBackground = True}
        TMS.Start(New ThreadedMessageData With {.IP = IP, .Data = data})
    End Sub

    ''' <summary>
    ''' Sends a message to every single client.
    ''' </summary>
    ''' <param name="data">The data to send.</param>
    ''' <remarks></remarks>
    Public Sub SendMessageToAll(ByVal data As Object)
        Dim SMTA As New Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf ThreadedMessageToAll)) With {.IsBackground = True}
        SMTA.Start(data)
    End Sub

    ''' <summary>
    ''' Sends a Chat Message to all the logged in chat people.
    ''' </summary>
    ''' <param name="data">The data to send.</param>
    ''' <remarks></remarks>
    Public Sub SendChatMessage(ByVal data As Object)
        Dim SMTA As New Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf ThreadedChatMessage)) With {.IsBackground = True}
        SMTA.Start(data)
    End Sub

    ''' <summary>
    ''' Returns an IP list and if they are in the chatroom.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnIPList() As List(Of String)
        Dim returnlist As New List(Of String)
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                returnlist.Add(If(ClientList(i).Loggedin, String.Format("{0} ({1})", ClientList(i).IP, ClientList(i).Username), ClientList(i).IP))
            Next
        End SyncLock
        Return returnlist
    End Function

    ''' <summary>
    ''' Threaded chat message sub.
    ''' </summary>
    ''' <param name="data">The data to send.</param>
    ''' <remarks></remarks>
    Private Sub ThreadedChatMessage(ByVal data As Object)
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                If ClientList(i).Loggedin Then ClientList(i).SendMessage(data)
            Next
        End SyncLock
    End Sub

    ''' <summary>
    ''' A threaded SendMessageToAll sub.
    ''' </summary>
    ''' <param name="data">The data to send.</param>
    ''' <remarks></remarks>
    Private Sub ThreadedMessageToAll(ByVal data As Object)
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                ClientList(i).SendMessage(data)
            Next
        End SyncLock
    End Sub

    ''' <summary>
    ''' This private sub is run from an additional process to send a message.
    ''' </summary>
    ''' <param name="data">The ThreadedMessageData structure.</param>
    ''' <remarks></remarks>
    Private Sub ThreadedSpecificMessage(ByVal data As Object)
        Dim NewData As ThreadedMessageData = CType(data, ThreadedMessageData)
        SyncLock ClientList
            For i As Integer = ClientList.Count - 1 To 0 Step -1
                With NewData
                    If ClientList(i).IP = .IP Then ClientList(i).SendMessage(NewData.Data)
                End With
            Next
        End SyncLock
    End Sub

End Class

''' <summary>
''' General Global Distribution Exception Class.
''' </summary>
''' <remarks></remarks>
Public Class GlobalDistributionException
    Inherits ApplicationException
    Private _StackTrace As String = String.Empty
    Public Property Origin As String = String.Empty
    Public Overrides ReadOnly Property StackTrace As String
        Get
            Return _StackTrace
        End Get
    End Property
    Public Sub New(ByVal stacktrace As String)
        _StackTrace = stacktrace
    End Sub
    Public Overrides Function ToString() As String
        Return String.Format("An error has originated from the class: {0}{1}The StackTrace is as follows:{1}{2}", Origin, Environment.NewLine, StackTrace)
    End Function
End Class

''' <summary>
''' Random Generation Class for strings.
''' </summary>
''' <remarks></remarks>
Public Class RandomStringMaker
    Dim ListChars As New List(Of String)({"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", _
                                          "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", _
                                          "u", "v", "w", "x", "y", "z", _
                                          "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", _
                                          "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", _
                                          "U", "V", "W", "X", "Y", "Z", _
                                          "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"})
    Dim History As New List(Of String)

    ''' <summary>
    ''' Generate a random string.
    ''' </summary>
    ''' <param name="length">The length of the string.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GenerateString(ByVal length As Integer) As String
        Dim RandNumb As New Random
        Dim StrBld As New System.Text.StringBuilder

        If length > 0 Then
            StrBld.Append(ListChars(RandNumb.Next(0, ListChars.Count - 1)))
            Do Until Not History.Contains(StrBld.ToString) AndAlso StrBld.Length >= length
                For i As Integer = 1 To length - 1
                    StrBld.Append(ListChars(RandNumb.Next(0, ListChars.Count - 1)))
                Next
            Loop
            History.Add(StrBld.ToString)
            Return StrBld.ToString
        Else
            Return String.Empty
        End If
    End Function

End Class

''' <summary>
''' This class handles the creation of Pokemon files that are uploaded to the server.
''' </summary>
''' <remarks></remarks>
Public Class PokemonHandler
    Private Searcher As SearchHandler
    ''' <summary>
    ''' Generates a new instance of the PokemonHandler class which handles the creation of uploaded Pokemon.
    ''' </summary>
    ''' <param name="SearchEngine">A reference to the current SeachHandler.</param>
    ''' <remarks></remarks>
    Public Sub New(ByRef SearchEngine As SearchHandler)
        Searcher = SearchEngine
    End Sub

    ''' <summary>
    ''' Creates a new Pokemon on the server if it doesn't exist already.
    ''' </summary>
    ''' <param name="information">The PokemonData.</param>
    ''' <remarks></remarks>
    Public Function CreateNewPokemon(ByVal information As DLL.PokemonData) As Boolean
        'Check for existing data.
        If Not CheckForExistingPokemon(information.ID) Then
            'No existing data, create filename.
            Dim PokemonFilename As String = Path.Combine(Searcher.CustomPokemonFiles, String.Format("{0}_{1}.pkm", New RandomStringMaker().GenerateString(5), information.PokemonName))
            'Check for encryption.
            information.PokemonData = CheckForEncryption(information.PokemonData)
            'Now create a text file.
            Using SW As New StreamWriter(Path.Combine(Searcher.CustomTextFiles, String.Format("{0}.txt", information.ID)))
                'name, id, path, internal name.
                SW.WriteLine(information.PokemonName.ToLower)
                SW.WriteLine(information.ID)
                SW.WriteLine(PokemonFilename)
                SW.WriteLine(information.IP)
                SW.WriteLine(New PokeHack.Pokemon(information.PokemonData).Name)
            End Using
            'Now write the Pokemon File
            File.WriteAllBytes(PokemonFilename, information.PokemonData)
            'And refresh the search engine.
            Searcher.Refresh()
            Return True
        End If
        Return False
    End Function

    ''' <summary>
    ''' Creates a new Textfile for the Pokemon Data.
    ''' </summary>
    ''' <param name="information">The Pokemon data.</param>
    ''' <remarks></remarks>
    Public Sub CreateNewTextFile(ByVal location As String, ByVal information As DLL.PokemonData)
        Using SW As New StreamWriter(Path.Combine(Searcher.TextFiles, String.Format("{0}.txt", information.ID)))
            'name, id, path, internal name.
            SW.WriteLine(information.PokemonName.ToLower)
            SW.WriteLine(information.ID)
            SW.WriteLine(location)
            SW.WriteLine(information.IP)
            SW.WriteLine(New PokeHack.Pokemon(information.PokemonData).Name)
        End Using
    End Sub

    ''' <summary>
    ''' Checks for an existing Pokemon. This is basically calling the
    ''' SearchHandler.CheckForExistingPokemon() function.
    ''' </summary>
    ''' <param name="ID">The ID to look for.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CheckForExistingPokemon(ByVal ID As String) As Boolean
        Return Searcher.CheckForExistingPokemon(ID)
    End Function

    ''' <summary>
    ''' Checsk for Pokemon data Encryption and decrypts it if necessary.
    ''' </summary>
    ''' <param name="data">The data to (possibly) decrypt.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function CheckForEncryption(ByVal data As Byte()) As Byte()
        If PokeHack.PokeHack.IsEncrypted(New PokeHack.Pokemon(data)) Then
            Return PokeHack.PokeHack.Decrypt(data).GetRawData
        Else
            Return data
        End If
    End Function

    ''' <summary>
    ''' Calculates the MD5 hash of a file.
    ''' </summary>
    ''' <param name="filepath">The Filepath to hash</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CalculateID(ByVal filepath As String) As String
        Return CalculateID(New FileStream(filepath, IO.FileMode.Open))
    End Function

    ''' <summary>
    ''' Calculates the MD5 of a stream.
    ''' </summary>
    ''' <param name="stream">The stream to hash</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CalculateID(ByVal stream As Stream) As String
        Return CalculateID(StreamToByteArray(stream))
    End Function

    ''' <summary>
    ''' Calculates the MD5 of a Byte Array
    ''' </summary>
    ''' <param name="bytes">The bytes to hash.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CalculateID(ByVal bytes As Byte()) As String
        Using md5 As New System.Security.Cryptography.MD5CryptoServiceProvider
            Dim hash() As Byte = md5.ComputeHash(bytes)
            Return ByteArrayToString(hash)
        End Using
    End Function

    ''' <summary>
    ''' Converts a Byte Array to a String.
    ''' </summary>
    ''' <param name="arrInput">The byte Array to convert.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ByteArrayToString(ByVal arrInput() As Byte) As String
        Dim sb As New System.Text.StringBuilder(arrInput.Length * 2)
        For i As Integer = 0 To arrInput.Length - 1
            sb.Append(arrInput(i).ToString("X2"))
        Next
        Return sb.ToString().ToLower
    End Function

    ''' <summary>
    ''' Converts a Stream to a Byte Array
    ''' </summary>
    ''' <param name="stream">The stream to convert</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function StreamToByteArray(ByVal stream As System.IO.Stream) As Byte()
        Dim streamLength As Integer = Convert.ToInt32(stream.Length)
        Dim fileData As Byte() = New Byte(streamLength) {}
        ' Read the file into a byte array
        stream.Read(fileData, 0, streamLength)
        stream.Close()
        Return fileData
    End Function

End Class

''' <summary>
''' This handles reading the .PKM files (kind of like a nice wrapper for PokeHack).
''' It also creates the DLL.Information structure for serialization.
''' </summary>
''' <remarks></remarks>
Public Class PokemonReader
    Private PokemonData As Byte()
    Private PokemonStructure As DLL.Information

    ''' <summary>
    ''' Creates a new PokemonReader Class that manipulates .PKM files.
    ''' </summary>
    ''' <param name="filepath">The .PKM file.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal filepath As String)
        PokeHack.Pokemon.LoadDataFiles()
        If File.Exists(filepath) Then
            PokemonData = File.ReadAllBytes(filepath)
            Call PopulateInformation()
        Else
            Throw New GlobalDistributionException(Environment.StackTrace) With {.Origin = String.Format("Pokemon.PokemonReader.New(). The file '{0}' doesn't exist", filepath)}
        End If

    End Sub

    ''' <summary>
    ''' Creates a new PokemonReader Class that manipulates .PKM files.
    ''' </summary>
    ''' <param name="data">The .PKM data.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal data As Byte())
        PokeHack.Pokemon.LoadDataFiles()
        PokemonData = data
        Call PopulateInformation()
    End Sub

    ''' <summary>
    ''' Returns the DLL.Information Structure.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnPokemonStructure() As DLL.Information
        Return PokemonStructure
    End Function

    ''' <summary>
    ''' Populates the structure with information.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub PopulateInformation()
        'Create the PokeHack data.
        Dim Pkm As New PokeHack.Pokemon(PokemonData)
        Dim IVs As New DLL.Information.IV
        Dim EVs As New DLL.Information.EV
        Dim CS As New DLL.Information.Stats
        'Now start the population.
        With PokemonStructure
            .Name = Pkm.Name
            .Gender = Pkm.Gender.ToString
            .Nature = Pkm.Nature.ToString
            .Origin = Pkm.Origin.ToString
            .OTName = Pkm.OTName
            .NickName = If(Pkm.IsNicknamed, Pkm.Nickname, String.Empty)
            'We can set the .InternalID later.
            .DexNumber = Pkm.Species
            .OTID = Pkm.OriginalTrainer
            .SID = Pkm.SecretID
            .Exp = CInt(Pkm.Experience)
            .Level = CInt(Pkm.Level)
            .Shiny = Pkm.IsShiny
            .FateFulEncounter = Pkm.IsFatefulEncounter
            .IsEgg = Pkm.IsEgg
            .Moves = Pkm.Moves
            With IVs
                .Attack = Pkm.AttIV.ToString
                .Defense = Pkm.DefIV.ToString
                .HP = Pkm.HPIV.ToString
                .Sp_Atk = Pkm.SPAttIV.ToString
                .Sp_Def = Pkm.SPDefIV.ToString
                .Speed = Pkm.SpeIV.ToString
            End With

            With EVs
                .Attack = Pkm.AttEV.ToString
                .Defense = Pkm.DefEV.ToString
                .HP = Pkm.HPEV.ToString
                .Sp_Atk = Pkm.SPAttEV.ToString
                .Sp_Def = Pkm.SPDefEV.ToString
                .Speed = Pkm.SpeEV.ToString
            End With

            With CS
                .Attack = Pkm.Attack.ToString
                .Defense = Pkm.Defense.ToString
                .HP = Pkm.HP.ToString
                .Sp_Atk = Pkm.SpecialAttack.ToString
                .Sp_Def = Pkm.SpecialDefense.ToString
                .Speed = Pkm.Speed.ToString
            End With

            .PkmIV = IVs
            .PkmEV = EVs
            .PkmStats = CS
        End With


    End Sub

End Class

''' <summary>
''' This handles reading the text files that each .PKM file has.
''' </summary>
''' <remarks></remarks>
Public Class PokemonTextReader

    ''' <summary>
    ''' Portable structure that shuffles around Pokemon Information inside
    ''' the GDS system.
    ''' </summary>
    ''' <remarks></remarks>
    Public Structure PokemonInformation
        ''' <summary>
        ''' The physical name of the Pokemon uploaded.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Name As String

        ''' <summary>
        ''' The unique ID of the Pokemon uploaded.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property ID As String

        ''' <summary>
        ''' The direct filepath to the uploaded Pokemon
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Path As String

        ''' <summary>
        ''' The IP address of the uploader.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property IP As String

        ''' <summary>
        ''' The actual name of the Pokemon.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property IName As String

    End Structure

    ''' <summary>
    ''' Variables.
    ''' </summary>
    ''' <remarks></remarks>
    Private _Name, _ID, _Path, _IP, _IName As String

    ''' <summary>
    ''' Returns the name of the uploaded Pokemon.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Name As String
        Get
            Return _Name
        End Get
    End Property

    ''' <summary>
    ''' Returns the unique ID of the Pokemon (not the PID).
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ID As String
        Get
            Return _ID
        End Get
    End Property

    ''' <summary>
    ''' Returns the Path to the .PKM file.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Path As String
        Get
            Return _Path
        End Get
    End Property

    ''' <summary>
    ''' Returns the uploader's IP.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property UploaderIP As String
        Get
            Return _IP
        End Get
    End Property

    ''' <summary>
    ''' Returns the actual name of the Pokemon.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property InternalName As String
        Get
            Return _IName
        End Get
    End Property

    ''' <summary>
    ''' Creates a new instance of the Pokemon Text reader class.
    ''' </summary>
    ''' <param name="file">The text file to read.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal file As String)
        If IO.File.Exists(file) Then
            Using SR As New IO.StreamReader(file)
                _Name = SR.ReadLine()
                _ID = SR.ReadLine()
                _Path = SR.ReadLine()
                _IP = SR.ReadLine()
                _IName = SR.ReadLine()
            End Using
        Else
            Throw New GlobalDistributionException(Environment.StackTrace) With {.Origin = "Pokemon.PokemonTextReader.New()"}
        End If

    End Sub

    ''' <summary>
    ''' Returns a PokemonInfo Structure of the currently loaded data.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnPokemonData() As PokemonInformation
        Dim Info As New PokemonInformation
        Info.Name = Me.Name
        Info.ID = Me.ID
        Info.Path = Me.Path
        Info.IP = Me.UploaderIP
        Info.IName = Me.InternalName
        Return Info
    End Function

End Class

''' <summary>
''' This class handles the indexing and searching of the .PKM and .TXT files of the GDS
''' Server.
''' </summary>
''' <remarks></remarks>
Public Class SearchHandler

    ''' <summary>
    ''' This List(Of String) holds the path to every single text file on the server.
    ''' </summary>
    ''' <remarks></remarks>
    Private BookList As New List(Of String)

    ''' <summary>
    ''' Returns the executing location of this assembly.
    ''' This is safe to do because this DLL has to be where
    ''' HyperGTS is, which would automatically place it in
    ''' the right area for this whole system to work.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property AssemblyPath As String
        Get
            Return IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.Location)
        End Get
    End Property

    ''' <summary>
    ''' Returns the location of custom text data files.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property CustomTextFiles As String
        Get
            Return Path.Combine(AssemblyPath, "CustomTextFiles")
        End Get
    End Property

    ''' <summary>
    ''' Returns the location of custom .PKM files.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property CustomPokemonFiles As String
        Get
            Return Path.Combine(AssemblyPath, "UploadedFiles")
        End Get
    End Property

    ''' <summary>
    ''' Returns the location of server created text files.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property TextFiles As String
        Get
            Return Path.Combine(AssemblyPath, "TextFiles")
        End Get
    End Property

    ''' <summary>
    ''' Returns the location of the server uploaded pokemon files.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property PokemonFiles As String
        Get
            Return Path.Combine(AssemblyPath, "PokemonFiles")
        End Get
    End Property

    ''' <summary>
    ''' Initializes the SearchHandler and creates the
    ''' directories if necessary.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        'Create the Custom Directories if necessary.
        If Not Directory.Exists(CustomPokemonFiles) Then Directory.CreateDirectory(CustomPokemonFiles)
        If Not Directory.Exists(CustomTextFiles) Then Directory.CreateDirectory(CustomTextFiles)
        'Check for the default directories and error if they don't exist.
        If Not Directory.Exists(TextFiles) OrElse Not Directory.Exists(PokemonFiles) Then
            Throw New GlobalDistributionException(Environment.StackTrace) With {.Origin = "SearchEngine.SearchHandler.New(). Missing Textfiles or PokemonFiles."}
        Else
            'Alright, fill in the search list.
            BookList.AddRange(Directory.GetFiles(CustomTextFiles, "*.txt"))
            BookList.AddRange(Directory.GetFiles(TextFiles, "*.txt"))
        End If
    End Sub

    ''' <summary>
    ''' Returns a special filtered list.
    ''' </summary>
    ''' <param name="FilterName">The name to search for.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnFilteredList(ByVal FilterName As String) As List(Of String)
        Dim StrBld As New List(Of String)
        SyncLock BookList
            For Each item As String In BookList
                Dim PD As New PokemonTextReader(item)
                If PD.Name.Contains(FilterName.Trim()) Then
                    StrBld.Add(String.Format("{0}|{1}|{2}|{3}", PD.Name, PD.ID, PD.UploaderIP, PD.InternalName))
                End If
            Next
        End SyncLock
        Return StrBld
    End Function

    ''' <summary>
    ''' Returns the entire list.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnUnfilteredList() As List(Of String)
        Dim StrBld As New List(Of String)
        SyncLock BookList
            For Each item As String In BookList
                Dim PD As New PokemonTextReader(item)
                StrBld.Add(String.Format("{0}|{1}|{2}|{3}", PD.Name, PD.ID, PD.UploaderIP, PD.InternalName))
            Next
        End SyncLock
        Return StrBld
    End Function

    ''' <summary>
    ''' Checks for an existing Pokemon.
    ''' </summary>
    ''' <param name="ID">The ID to search for.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CheckForExistingPokemon(ByVal ID As String) As Boolean
        SyncLock BookList
            For Each item As String In BookList
                If Path.GetFileNameWithoutExtension(item) = ID Then Return True
            Next
        End SyncLock
        Return False
    End Function

    ''' <summary>
    ''' Returns the Filepath of the text file we're looking for.
    ''' </summary>
    ''' <param name="ID">The ID to search for.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnFilePath(ByVal ID As String) As String
        SyncLock BookList
            For Each item As String In BookList
                If New PokemonTextReader(item).ID = ID Then Return item
            Next
        End SyncLock
        Return String.Empty
    End Function

    ''' <summary>
    ''' Refreshes the List.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Refresh()
        SyncLock BookList
            BookList.Clear()
            BookList.AddRange(Directory.GetFiles(CustomTextFiles, "*.txt"))
            BookList.AddRange(Directory.GetFiles(TextFiles, "*.txt"))
        End SyncLock

    End Sub

    ''' <summary>
    ''' Returns the size of the database.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnCount() As Integer
        Return BookList.Count()
    End Function

End Class

Public Class DNS_Main

    Dim dnsserv, s As Socket

    Public Property fakeIP As String = String.Empty
    Dim localIP As String = "127.0.0.1"

    Dim log As String = String.Empty

    Dim mustLog As Boolean = False
    Dim autoConf As Boolean = False
    Dim localOnly As Boolean = False
    Dim noLocal As Boolean = False
    Dim useAutoIPSwitching As Boolean = True

    Dim defaultDNSServer As String = My.Settings.defaultDNSServer()
    Public Property Abort As Boolean = False
    Dim thr As Thread = New Thread(AddressOf dnsspoof)

    Public Event Logger(ByVal log As String)

    Public Sub Writeline(Optional ByVal data As String = "")
        RaiseEvent Logger(data)
    End Sub

    ''' <summary>
    ''' Returns the Internal Number of the GTS.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Version As Version
        Get
            Return New Version(0I, 4I, 155I, 10I)
        End Get
    End Property

    Public Sub [Start]()

        Writeline("*** Fake DNS server v0.4 by ShinyJirachi ***")
        Writeline("*** Based on M@T's Fake DNS server v0.3 ***")
        Writeline("*** Based on LordLandon's sendpkm.py ***")

        If Not IsIPv4(Me.fakeIP) Then
            Writeline("*** Invalid IP Address: " & Me.fakeIP & " ***")
            Writeline("*** Please restart the server with a valid IP! ***")
        Else
            thr.Start()
        End If
    End Sub

    Public Sub [Stop]()
        Try
            dnsserv.Close()
        Catch ex As Exception
            Writeline(ex.ToString)
        End Try
        If thr.IsAlive() Then thr.Abort()
        thr = New Thread(AddressOf dnsspoof)
    End Sub

    Public Function GetExternalIP() As String
        Try
            Return New StreamReader(HttpWebRequest.Create("http://whatismyip.com/automation/n09230945.asp").GetResponse().GetResponseStream()).ReadToEnd
        Catch
            Return String.Empty
        End Try
    End Function

    Private Sub dnsspoof()
        dnsserv = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
        dnsserv.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1)

        dnsserv.Bind(New IPEndPoint(IPAddress.Any, 53))

        Writeline("*** DNS Server Initialized ***")

        Dim fakeIP_bytes(3) As Byte
        For i As Integer = 0 To 3
            fakeIP_bytes(i) = CByte(CInt(fakeIP.Split("."c)(i)))
        Next i

        Dim spoofedIP As String = String.Empty
        Dim spoofedIP_bytes As Byte()
        Dim r(511), rr(511) As Byte
        Dim lngSnd, lngRcv As Integer
        Dim str_r, str_rr, rawHost As String

        Dim rem_ep As EndPoint = New IPEndPoint(IPAddress.Any, 0)

        While True
            Try
                ReDim r(511)
                lngRcv = dnsserv.ReceiveFrom(r, SocketFlags.None, rem_ep)

                ReDim Preserve r(lngRcv)

                str_r = Encoding.ASCII.GetString(r, 0, lngRcv)

                Writeline("Incoming data from : " & CType(rem_ep, IPEndPoint).Address.ToString())

                rawHost = str_r.Substring(12, str_r.IndexOf(Chr(0), 12) - 12)

                Dim requestedHost As String = ""
                Dim n As Integer = 0

                While n < rawHost.Length
                    requestedHost &= rawHost.Substring(n + 1, r(n + 12)) & "."
                    n += r(n + 12) + 1
                End While
                requestedHost = requestedHost.Substring(0, requestedHost.Length - 1)

                Writeline(CType(rem_ep, IPEndPoint).Address.ToString() & "  requesting : " & requestedHost)

                ReDim rr(511)

                s = New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
                s.ReceiveTimeout = 2000
                s.SendTimeout = 2000
                s.Connect(defaultDNSServer, 53)
                s.Send(r, lngRcv, SocketFlags.None)
                lngSnd = s.Receive(rr, SocketFlags.None)

                ReDim Preserve rr(lngSnd)

                If requestedHost = "gamestats2.gs.nintendowifi.net" Then
                    spoofedIP = fakeIP
                    spoofedIP_bytes = fakeIP_bytes

                    Array.Copy(spoofedIP_bytes, 0, rr, &H3C, 4)
                    Writeline("Spoofing " & requestedHost & " -> " & spoofedIP & " for " & CType(rem_ep, IPEndPoint).Address.ToString())

                End If

                str_rr = Encoding.ASCII.GetString(rr, 0, lngSnd)

                dnsserv.SendTo(rr, rr.Length, SocketFlags.None, rem_ep)
                Writeline("Data sent to : " & CType(rem_ep, IPEndPoint).Address.ToString())

            Catch ex As ThreadAbortException
                Writeline("Got interrupt signal, aborting...")
            Catch ex As Exception
                Writeline("Error : " & ex.Message)
            End Try
        End While
    End Sub

    Public Function IsIPv4(ByVal ip As String) As Boolean
        Dim rIP As Regex = New Regex("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")
        Return rIP.IsMatch(ip)
    End Function

    Private Function GetTime() As String
        Return "[" & Now.ToString("dd/MM/yy HH:mm:ss") & "] "
    End Function

    Private Function IsLocalIP(ByVal ip As String) As Boolean
        'Private IP ranges are :
        '192.168.0.0 - 192.168.255.255
        '172.16.0.0 - 172.31.255.255
        '10.0.0.0 - 10.255.255.255
        With ip
            Return .StartsWith("192.168.") _
                   OrElse .StartsWith("10.") _
                   OrElse (.StartsWith("172.") _
                           AndAlso (CDbl(.Split("."c)(1)) >= 16 _
                                    AndAlso CDbl(.Split("."c)(1)) < 32))
        End With
    End Function

End Class

Public Class HyperInteract

    Public Property RequestPath As String
    Public Property DataText As String
    Public Property CustomText As String

    ''' <summary>
    ''' Creates a new HyperGTS Interaction Instance.
    ''' </summary>
    ''' <param name="InitialPath">Pass in Application.StartupPath</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal InitialPath As String)
        Me.RequestPath = IO.Path.Combine(InitialPath, "UserRequests")
        Me.DataText = IO.Path.Combine(InitialPath, "TextFiles")
        Me.CustomText = IO.Path.Combine(InitialPath, "CustomTextFiles")
    End Sub

    ''' <summary>
    ''' Checks to see if the specified IP Address has a reservation.
    ''' </summary>
    ''' <param name="IP">The IP Address.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CheckIP(ByVal IP As String) As Boolean
        For Each File As String In Directory.GetFiles(RequestPath, "*.txt")
            If Path.GetFileNameWithoutExtension(File) = IP Then Return True
        Next
        Return False
    End Function

    ''' <summary>
    ''' Returns the specific Pokemon the IP address has reserved.
    ''' </summary>
    ''' <param name="IP">The IP Address.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ReturnPokemonFile(ByVal IP As String) As String
        'Get the text file.
        Dim ID As String = String.Empty
        Dim ReservationPath As String = IO.Path.Combine(RequestPath, String.Format("{0}.txt", IP))
        Using SR As New StreamReader(ReservationPath)
            ID = SR.ReadLine()
        End Using

        'Remove the reservation.
        'File.Delete(ReservationPath)

        'Now look in the Data files.
        Dim QList As New List(Of String)
        QList.AddRange(Directory.GetFiles(DataText))
        QList.AddRange(Directory.GetFiles(CustomText))
        For Each pkm As String In QList
            Dim CurrentPKM As New PokemonInformation(pkm)
            If CurrentPKM.ID = ID Then Return CurrentPKM.Path
        Next
        Return String.Empty
    End Function

    ''' <summary>
    ''' Creates a Pokemon from uploaded data.
    ''' </summary>
    ''' <param name="data">The uploaded data</param>
    ''' <param name="IP">The IP that uploaded it.</param>
    ''' <remarks></remarks>
    Public Sub CreatePokemonDataFromUploaded(ByVal data As Byte(), ByVal IP As String)
        'We have the raw data man. Fucking EPIC!
        'Now get some info up in here.
        Dim SearchHandler As New SearchHandler()
        Dim PokeHandler As New PokemonHandler(SearchHandler)
        Dim Info As New DLL.PokemonData
        Info.ID = PokeHandler.CalculateID(data)
        Info.IP = IP
        Info.PokemonData = data
        With New PokeHack.Pokemon(data)
            Info.PokemonName = If(.IsNicknamed, String.Format("{0}_{1}", .Name, .Nickname), .Name)
        End With

        Info.PokemonName = New PokeHack.Pokemon(data).Name

        'Now create the Pokemon.
        If Not PokeHandler.CheckForExistingPokemon(Info.ID) Then PokeHandler.CreateNewPokemon(Info)

    End Sub

    ''' <summary>
    ''' Delete the Reservation Path.
    ''' </summary>
    ''' <param name="IP"></param>
    ''' <remarks></remarks>
    Public Sub DeleteReservation(ByVal IP As String)
        Dim ReservationPath As String = IO.Path.Combine(RequestPath, String.Format("{0}.txt", IP))
        If File.Exists(ReservationPath) Then File.Delete(ReservationPath) Else Throw New GlobalDistributionException(Environment.StackTrace.ToString) With {.Origin = "HyperInteract.HyperInteract.DeleteReservation(" & IP & ")"}
    End Sub

    Private Structure PokemonInformation
        Public Property Name As String
        Public Property ID As String
        Public Property Path As String
        Public Property IP As String
        Public Sub New(ByVal file As String)
            Using SR As New IO.StreamReader(file)
                Name = SR.ReadLine()
                ID = SR.ReadLine()
                Path = SR.ReadLine()
                IP = SR.ReadLine()
            End Using
        End Sub
    End Structure

End Class

Public Class GTS

    ''' <summary>
    ''' Used to determine how the system distribution method is setup.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property DistributionMode As ModeType = ModeType.Receive

    ''' <summary>
    ''' Returns the maximum number of allowed connections on the server.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property MaximumConnections As Integer
        Get
            Return _maxConnections
        End Get
    End Property

    ''' <summary>
    ''' Returns the current number of connections.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property CurrentConnections As Integer
        Get
            Return _clientlist.Count
        End Get
    End Property

    ''' <summary>
    ''' Returns the External IP address.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ExternalIP As String
        Get
            Return _dns.GetExternalIP()
        End Get
    End Property

    ''' <summary>
    ''' Returns the executing location of this assembly.
    ''' This is safe to do because this DLL has to be where
    ''' HyperGTS is, which would automatically place it in
    ''' the right area for this whole system to work.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property AssemblyPath As String
        Get
            Return IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly.Location)
        End Get
    End Property

    ''' <summary>
    ''' Returns how many Pokemon the GTS has sent.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property SentPokemon As Integer
        Get
            Return _pokemonSent
        End Get
    End Property

    ''' <summary>
    ''' Gets or Sets the Distribution Directory.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property PokemonFolder As String
        Get
            Return _pokemonfolder
        End Get
        Set(ByVal value As String)
            If Directory.Exists(value) Then
                _pkmfolderlist.Clear()
                _pkmfolderlist.AddRange(Directory.GetFiles(value, "*.pkm"))
                _pokemonfolder = value
            End If
        End Set
    End Property

    ''' <summary>
    ''' Gets or Sets the individual file to distribute.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property PokemonFile As String
        Get
            Return _individual
        End Get
        Set(ByVal value As String)
            If File.Exists(value) Then _individual = value
        End Set
    End Property

    Private _individual As String = String.Empty
    Private _pokemonfolder As String = String.Empty
    Private _pkmfolderlist As New List(Of String)
    Private _currentPokemon As Integer = 0
    Private _pokemonSent As Integer = 0
    Private _maxConnections As Integer = 50I
    Private _gds As GDS
    Private _dns As DNS_Main
    Private ReadOnly _hi As New HyperInteract(AssemblyPath)
    Private _gtsserver As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
    Private _clientlist As New List(Of Socket)
    Private _loopthread As New Thread(AddressOf ListenLoop)
    Private _saea As New SocketAsyncEventArgs()
    Private _readyToAccept As Boolean = True

    ''' <summary>
    ''' Logging Event.
    ''' </summary>
    ''' <param name="text">Text to log.</param>
    ''' <remarks></remarks>
    Public Event LogEvent(ByVal text As String)

    ''' <summary>
    ''' Raised when a Pokemon is sent out.
    ''' </summary>
    ''' <param name="number">The number of pokemon that have been sent.</param>
    ''' <remarks></remarks>
    Public Event PokemonCount(ByVal number As Integer)

    ''' <summary>
    ''' Used to determine what mode the GTS runs in.
    ''' </summary>
    ''' <remarks></remarks>
    Public Enum ModeType

        ''' <summary>
        ''' Sends out one Pokemon File, period.
        ''' Set this via the PokemonFile Property.
        ''' </summary>
        ''' <remarks></remarks>
        Invididual = 1

        ''' <summary>
        ''' Sends out a folder in alphabetical order. Set the folder path via the
        ''' PokemonFolder Property.
        ''' </summary>
        ''' <remarks></remarks>
        Folder = 2

        ''' <summary>
        ''' Sends out a folder in a randomized order. Set the folder path via the PokemonFolder Property.
        ''' </summary>
        ''' <remarks></remarks>
        RandomFolder = 3

        ''' <summary>
        ''' Disables sending Pokemon unless there's a reservation placed.
        ''' Uploads enabled MUST be true for this mode to work right.
        ''' </summary>
        ''' <remarks></remarks>
        Receive = 4
    End Enum

    Private Structure ReqStruct
        Public sock As Socket
        Public req As Request
    End Structure

    ''' <summary>
    ''' Creates a new instance of the Global Trade Server emulator.
    ''' </summary>
    ''' <param name="MaxConnections">The maximum number of allowed connections, recommended at 50.</param>
    ''' <remarks></remarks>
    Public Sub New(ByRef GdsServer As GDS, ByRef DnsServer As DNS_Main, Optional ByVal MaxConnections As Integer = 50I)
        'Set socket options.
        _gtsserver.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1)
        _maxConnections = MaxConnections
        _gds = GdsServer
        _dns = DnsServer
    End Sub

    ''' <summary>
    ''' Start the GTS Server.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Start()
        Try
            AddHandler _saea.Completed, AddressOf SocketHandler
            Me.Log("Setting up the GTS Server")
            'Bind the server.
            _gtsserver.Bind(New IPEndPoint(IPAddress.Any, 80I))
            Me.Log("GTS initialization on Port 80 complete.")
            'Start listening.
            _gtsserver.Listen(MaximumConnections)
            'Setup the loop thread.
            _loopthread.IsBackground = True
            'Start.
            _loopthread.Start()
        Catch ex As SocketException
            Me.Log("There was a problem starting the server:")
            Me.Log(ex.ToString)
        Catch ex As Exception
            Me.Log("There was a general error with the server:")
            Me.Log(ex.ToString)
        End Try
    End Sub

    ''' <summary>
    ''' Stops the GTS Server.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub [Stop]()
        For Each item As Socket In _clientlist
            item.Close()
        Next
        Try
            _loopthread.Abort()
        Catch ex As Exception

        End Try
        _saea.Dispose()
        _gtsserver.Close()
    End Sub

    ''' <summary>
    ''' Listener Loop
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub ListenLoop()
        While True
            If _readyToAccept Then
                'Me.Log("Ready to accept a new client")
                _readyToAccept = False
                Call AcceptAction()
            End If
            Thread.Sleep(30)
            'Me.Log("sleep")
        End While
    End Sub

    ''' <summary>
    ''' Runs the accepting routine.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub AcceptAction()
        _saea.AcceptSocket = Nothing
        Dim saea As New SocketAsyncEventArgs
        AddHandler saea.Completed, AddressOf SocketHandler
        If Not _gtsserver.AcceptAsync(saea) Then
            Call SocketHandler(Nothing, saea)
        End If
        '_readyToAccept = True
    End Sub

    ''' <summary>
    ''' Sets up the socket shit.
    ''' </summary>
    ''' <param name="client">Client Socket</param>
    ''' <remarks></remarks>
    Private Sub SocketHandler(ByVal sender As Object, ByVal client As SocketAsyncEventArgs)
        RemoveHandler client.Completed, AddressOf SocketHandler
        'Check to see if we're OK, if not OK, return
        _readyToAccept = True
        If client.SocketError <> SocketError.Success Then Return
        Dim DS_User As Socket = client.AcceptSocket
        'We have the socket, now start processing them.
        'Create a new thread
        Dim th As New Thread(New ParameterizedThreadStart(AddressOf ThreadPoolHandler)) With {.IsBackground = True}
        'Run it.
        th.Start(DS_User)
    End Sub

    ''' <summary>
    ''' Spins up to handle the asyncReq() sub
    ''' </summary>
    ''' <param name="sock">The socket to bullshit around.</param>
    ''' <remarks></remarks>
    Private Sub ThreadPoolHandler(ByVal sock As Object)
        Dim sock2 As Socket = CType(sock, Socket)
        Try
            SyncLock _clientlist
                _clientlist.Add(sock2)
            End SyncLock
            'Run AsyncReq
            asyncReq(sock2)
        Catch ex As Exception
            Me.Log(ex.ToString)
        Finally
            If _clientlist.Contains(sock2) Then
                _clientlist.Remove(sock2)
            End If
            sock2.Close()
        End Try
    End Sub

#Region " Ported Shit "


    Private Function GetBin(ByVal currentDataFileName As String) As Byte()
        Dim bin As Byte() = Nothing
        If File.Exists(currentDataFileName) Then
            Dim pkm As Byte() = Nothing

            pkm = File.ReadAllBytes(currentDataFileName)
            bin = HyperGTS.Pokemon.EncryptPokemon(DirectCast(pkm.Clone(), Byte()))

            If pkm.Length < 236 Then
                Array.Resize(pkm, 236)
            ElseIf pkm.Length > 236 Then
                Me.Log("Error : incorrect file size !")
                Return Nothing
            End If

            Dim binEnd As Byte() = New Byte(55) {}
            Array.Copy(pkm, 8, binEnd, 0, 2)
            'ID
            binEnd(2) = CByte(If((pkm(64) And 4) > 0, 3, (pkm(64) And 2) + 1))
            'Gender
            binEnd(3) = pkm(140)
            'Level
            Array.Copy(New Byte() {&H1, &H0, &H3, &H0, &H0, &H0, _
             &H0, &H0}, 0, binEnd, 4, 8)
            'Requesting Bulba, either, any
            Array.Copy(pkm, &H68, binEnd, 32, 16)
            'OT name
            Array.Copy(pkm, &HC, binEnd, 48, 2)
            'OT ID
            Array.Resize(bin, 292)
            Array.Copy(binEnd, 0, bin, 236, 56)
        End If
        Return bin
    End Function

    Private Shared Function File2PKMBytes(ByVal filename As String) As Byte()
        Dim bin As Byte()
        Dim pkm As Byte() = Nothing

        Select Case Path.GetExtension(filename).ToLower()
            Case ".pkm"
                pkm = File.ReadAllBytes(filename)
                bin = HyperGTS.Pokemon.EncryptPokemon(DirectCast(pkm.Clone(), Byte()))

                Exit Select
            Case ".bin"
                bin = File.ReadAllBytes(filename)
                pkm = HyperGTS.Pokemon.DecryptPokemon(DirectCast(bin.Clone(), Byte()))
                Exit Select
        End Select

        Array.Resize(pkm, 236)

        Dim binEnd As Byte() = New Byte(55) {}
        Array.Copy(pkm, 8, binEnd, 0, 2)
        'ID
        binEnd(2) = If((pkm(64) And 4) > 0, CByte(3), CByte((pkm(64) And 2) + 1))
        'Gender
        binEnd(3) = pkm(140)
        'Level
        Array.Copy(New Byte() {&H1, &H0, &H3, &H0, &H0, &H0, _
         &H0, &H0}, 0, binEnd, 4, 8)
        'Requesting Bulba, either, any
        Array.Copy(pkm, &H68, binEnd, 32, 16)
        'OT name
        Array.Copy(pkm, &HC, binEnd, 48, 2)
        'OT ID
        bin = New Byte(291) {}
        Array.Resize(bin, 292)
        Array.Copy(binEnd, 0, bin, 236, 56)

        Return bin
    End Function

    Private Shared Function GetReq(ByRef sock As Socket) As ReqStruct
        'Dim sock As Socket = serv.Accept()
        Dim a As Byte() = New Byte(sock.ReceiveBufferSize - 1) {}

        Dim cnt As Integer = sock.Receive(a, sock.ReceiveBufferSize, SocketFlags.None)
        Array.Resize(a, cnt)

        Dim data As String = Encoding.GetEncoding("iso-8859-1").GetString(a)

        Dim ret As New ReqStruct()
        ret.req = New GlobalDistributionServer.Request(data)
        ret.sock = sock

        Return ret
    End Function

    Private Shared Function SendResp(ByRef sock As Socket, ByVal data As Object) As Integer
        Dim resp As Response = If(TypeOf data Is Response, DirectCast(data, Response), New Response(data.ToString()))
        Return sock.Send(Encoding.GetEncoding("iso-8859-1").GetBytes(resp.ToString()))
    End Function

    Private Shared Function RespFromServ(ByVal req As Request) As Response
        Dim s As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        s.Connect("207.38.11.146", 80)
        s.Send(Encoding.GetEncoding("iso-8859-1").GetBytes(req.ToString()))

        Dim a As Byte() = New Byte(8191) {}

        Dim cnt As Integer = s.Receive(a, 8192, SocketFlags.None)
        Array.Resize(a, cnt)

        Dim data As String = Encoding.GetEncoding("iso-8859-1").GetString(a)

        Return New Response(data)
    End Function

    Private Shared Function RandStr(ByVal len As Integer) As String
        Dim functionReturnValue As String = Nothing
        Dim pop As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
        functionReturnValue = ""
        Dim rnd As New Random()

        For i As Integer = 1 To len
            functionReturnValue += pop(rnd.[Next](0, pop.Length))
        Next
        Return functionReturnValue
    End Function

    Private Function Decrypt_data(ByVal data As Byte()) As Byte()
        Dim obf_key_blob As List(Of Byte) = ArrayToList(data).GetRange(0, 4)

        If BitConverter.IsLittleEndian Then
            obf_key_blob.Reverse()
        End If

        Dim message As Byte() = ArrayToList(data).GetRange(4, data.Length - 4).ToArray()
        Dim obf_key As UInt32 = BitConverter.ToUInt32(obf_key_blob.ToArray(), 0)

        Dim key As UInt32 = CUInt(obf_key Xor &H4A3B2C1D)

        Return Stream_decipher(message, New HyperGTS.GTS_PRNG(key)).ToArray()
    End Function

    Private Function Stream_decipher(ByVal data As Byte(), ByVal keystream As HyperGTS.GTS_PRNG) As List(Of Byte)
        Dim functionReturnValue As List(Of Byte) = Nothing
        functionReturnValue = New List(Of Byte)()

        For Each c As Byte In data
            functionReturnValue.Add(CByte((c Xor keystream.NextNum()) And &HFF))
        Next
        Return functionReturnValue
    End Function

    'Private PKMfromFolder As List(Of String)
    'Private CountPKMfromFolder As Integer

    Private Function ArrayToList(ByVal data As Byte()) As List(Of Byte)
        Return New List(Of Byte)(data)
    End Function

#End Region

    Private currentDataFileName As String = ""

    ''' <summary>
    ''' Gets a random Pokemon from the Pokemon List.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetRandomPokemon() As String
        ' New Random Object.
        Dim rnd As New Random()
        If Me.DistributionMode = ModeType.RandomFolder Then Return _pkmfolderlist(rnd.[Next](0, _pkmfolderlist.Count)) Else Return String.Empty
    End Function

    ''' <summary>
    ''' Handles all the Async shit for the user.
    ''' </summary>
    ''' <param name="sk">The socket.</param>
    ''' <remarks></remarks>
    Private Sub asyncReq(ByVal sk As Socket)
        Dim ansStr As String = Nothing
        Dim ans As Byte() = {}
        Dim reqst As ReqStruct = Nothing
        Dim data As Byte() = New Byte(511) {}
        Dim ip As String = sk.RemoteEndPoint.ToString().Split(":"c)(0)
        Try
            reqst = GetReq(sk)
        Catch ex As Exception
            Me.Log("Got wrong request, probably not from a DS. Error was : " + ex.Message)
            Return
        End Try

        Dim sock As Socket = reqst.sock
        Dim req As Request = reqst.req

        Dim pid As String = req.getvars("pid")

        If req.getvars.Count = 1 Then
            ansStr = RandStr(32)
        Else
            Dim encrypted_data As Byte() = System.Convert.FromBase64String(req.getvars("data").Replace("-", "+").Replace("_", "/"))
            data = Decrypt_data(encrypted_data)
            data = ArrayToList(data).GetRange(4, data.Length - 4).ToArray()

            Select Case req.action
                Case "info"

                    ' Check for a ban!
                    If Not _gds.BanHandler.IsIPBanned(ip) Then
                        ' Someone has entered the GTS. Log it.
                        Me.Log(ip + " has entered the GTS.")
                        ans = New Byte() {1, 0}
                    Else
                        ' If they didn't get the answer they wanted, the DS will hurr durr.
                        Me.Log(ip + " is on the ban list!")
                    End If
                    Exit Select

                Case "setProfile"

                    ans = New Byte() {0, 0, 0, 0, 0, 0, 0, 0}
                    Me.Log("Profile from " + ip)
                    Exit Select

                Case "result"

                    ' LOG!
                    Me.Log(ip + " is checking for a reservation.")

                    'Step 1: See if we have a reservation.
                    CheckForReservation(sk.RemoteEndPoint.ToString)

                    'Step 2: See if the FileName is still empty.
                    If currentDataFileName = String.Empty Then
                        Me.Log(ip + " has no reservation")
                        'Step 3: Determine what mode we're in.
                        Select Case Me.DistributionMode
                            Case ModeType.Invididual 'Single File.
                                currentDataFileName = _individual
                                Me.Log(ip + " will get " + Path.GetFileNameWithoutExtension(currentDataFileName))
                                ans = GetBin(currentDataFileName)
                            Case ModeType.Folder 'Sequential folder.
                                currentDataFileName = _pkmfolderlist(_currentPokemon)
                                Me.Log(ip + " will get " + Path.GetFileNameWithoutExtension(currentDataFileName))
                                ans = GetBin(currentDataFileName)
                            Case ModeType.RandomFolder 'Random folder.
                                currentDataFileName = GetRandomPokemon()
                                Me.Log(ip + " will get " + Path.GetFileNameWithoutExtension(currentDataFileName))
                                ans = GetBin(currentDataFileName)
                            Case ModeType.Receive
                                ans = New Byte() {5, 0}
                        End Select
                    Else
                        Me.Log(ip + " has a reservation!")
                        ans = GetBin(currentDataFileName)
                    End If

                Case "get"

                    ' Report a Get Request.
                    Me.Log("Get from " + sk.RemoteEndPoint.ToString().Split(":"c)(0))
                    If File.Exists(currentDataFileName) Then
                        ans = GetBin(currentDataFileName)
                    End If
                    Exit Select

                Case "return"

                    ' Lol, right, this'll ever happen.
                    Me.Log("Return from " + sk.RemoteEndPoint.ToString().Split(":"c)(0))
                    Exit Select

                Case "delete"

                    ' Do another reservation check to get the name set.
                    CheckForReservation(sk.RemoteEndPoint.ToString())

                    ' Now get the IP setup and report a request to the Logger.
                    Me.Log("Sending Pokemon to " & ip)

                    ' Create the Pokemon Name variable.
                    Dim pkmname As String = String.Empty

                    ' Setup a Try catch since this is the most error prone code ever -_-
                    Try
                        ' Load the current data.
                        Dim delData As Byte() = GetBin(currentDataFileName)
                        Dim dPH As New PokeHack.Pokemon(delData)

                        'Check for encryption, decrypt if necessary.
                        If PokeHack.PokeHack.IsEncrypted(dPH) Then
                            dPH = PokeHack.PokeHack.Decrypt(dPH.GetRawData())
                        End If

                        ' Now set the name.
                        pkmname = dPH.Name

                        ' Tell the log.
                        Me.Log(" * Sending " & pkmname & " ( " & Path.GetFileName(currentDataFileName) & " )  to " & ip)

                        ' Increase the number sent and tell the updater.
                        _pokemonSent += 1
                        RaiseEvent PokemonCount(_pokemonSent)
                        'BGW_GTS.ReportProgress(4, String.Empty)

                        ' Run the post routines.
                        Me.Log(" Running Randomizer() and PostSendChanger() routines ")

                        PostSendChanger()
                        Randomizer()


                        ' If there was a reservation, it was received, so delete it.

                        If _hi.CheckIP(ip) Then
                            _hi.DeleteReservation(ip)
                            Me.Log(ip & "'s reservation has been deleted.")

                        End If
                    Catch ex As Exception
                        ' Goddamn it! ARGH RAGE
                        Me.Log("Error: " + ex.Message)
                        Me.Log("Stack:" + ex.StackTrace)
                    End Try

                    Exit Select

                Case "post"

                    ' Get Information
                    Dim binData As Byte() = ArrayToList(data).GetRange(0, 236).ToArray()
                    Dim postData__1 As Byte() = ArrayToList(data).GetRange(236, data.Length - 236).ToArray()

                    ' Now we have the Pokemon Data.
                    Dim pkmData As Byte() = HyperGTS.Pokemon.DecryptPokemon(binData)
                    Dim PH As New PokeHack.Pokemon(pkmData)

                    ' Report about it.
                    Me.Log(" A " + PH.Name & " has been uploaded by " + sk.RemoteEndPoint.ToString().Split(":"c)(0) & ".")
                    Me.Log("It is a " & PH.Gender.ToString() & ". Level: " & PH.Level.ToString())

                    If Me.DistributionMode = ModeType.Receive Then
                        _hi.CreatePokemonDataFromUploaded(pkmData, sk.RemoteEndPoint.ToString().Split(":"c)(0))
                    Else
                        Me.Log(" Uploads are disabled. " + PH.Name & " was rejected.")
                    End If


                    ' Reject it to stay on the game.
                    ans = New Byte() {&HC, 0}

                    ' Refresh the directories.
                    _gds.RefreshSearcher()

                    Exit Select

                Case "post_finish"

                    ' Post Finish.
                    Me.Log("Report finish from " + ip)
                    ans = New Byte() {1, 0}
                    Exit Select

                Case "search"

                    HyperGTS.Pokemon.InitializeDictionaries()
                    Dim postdata__2 As Byte() = ArrayToList(data).GetRange(0, 2).ToArray()
                    Dim pkmnnr As UInt16 = BitConverter.ToUInt16(postdata__2, 0)
                    Dim pkmnname As String = HyperGTS.Pokemon.dPKMSpecies(pkmnnr)
                    Me.Log("Searched for: #" & pkmnnr & " " & pkmnname)
                    Exit Select

            End Select
            Dim E As Encoding = Encoding.GetEncoding("iso-8859-1")
            ansStr = E.GetString(ans)
        End If

        SendResp(sock, ansStr)
    End Sub

    ''' <summary>
    ''' Checks for a reservation and assigns it to the currentDataFileName.
    ''' </summary>
    ''' <param name="ipaddr"></param>
    ''' <remarks></remarks>
    Private Sub CheckForReservation(ByVal ipaddr As String)
        Dim ip As [String] = ipaddr.Split(":"c)(0)

        'Step 1: Check for the actual reservation.
        If _hi.CheckIP(ip) Then
            'Step 2: Reservation found, assign data.
            currentDataFileName = _hi.ReturnPokemonFile(ip)
        End If

    End Sub

    ''' <summary>
    ''' Gets the next Pokemon Setup.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub Randomizer()
        Select Case DistributionMode
            Case ModeType.Invididual
                currentDataFileName = _individual
            Case ModeType.Folder
                currentDataFileName = _pkmfolderlist(_currentPokemon)
            Case ModeType.RandomFolder
                currentDataFileName = GetRandomPokemon()
        End Select
    End Sub

    ''' <summary>
    ''' Runs after seting the Pokemon.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub PostSendChanger()
        Select Case Me.DistributionMode
            Case ModeType.Folder
                If (_currentPokemon) = _pkmfolderlist.Count - 1 Then
                    _currentPokemon = 0
                Else
                    _currentPokemon += 1
                End If

                currentDataFileName = _pkmfolderlist(_currentPokemon)
                Dim FI As New FileInfo(currentDataFileName)
                Me.Log("Next Pokemon will be: " + FI.Name)

        End Select
    End Sub

    ''' <summary>
    ''' General Log.
    ''' </summary>
    ''' <param name="text">Log Data.</param>
    ''' <remarks></remarks>
    Private Sub Log(ByVal text As String)
        RaiseEvent LogEvent(text)
    End Sub

    ''' <summary>
    ''' Returns the Internal Number of the GTS.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Version As Version
        Get
            Return New Version(1I, 4I, 120I, 1I)
        End Get
    End Property

End Class

Public Class Request
    Public action As String
    Public page As String
    Public getvars As Dictionary(Of String, String)

    Public Sub New()
        action = Nothing
        page = Nothing
        getvars = New Dictionary(Of String, String)()
    End Sub

    Public Sub New(ByVal h As String)
        If Not h.StartsWith("GET") Then
            Throw New Exception("Not a DS header !")
        End If

        Dim request__1 As String = ""

        request__1 = h.Substring(h.IndexOf("/pokemondpds/") + 13, h.IndexOf("HTTP/1.1") - h.IndexOf("/pokemondpds/") - 14)

        page = request__1.Substring(0, request__1.IndexOf("?"))
        action = request__1.Substring(request__1.IndexOf("/") + 1, request__1.IndexOf(".asp?") - request__1.IndexOf("/") - 1)

        Dim vars As New Dictionary(Of String, String)()

        For Each i As String In request__1.Substring(request__1.IndexOf("?") + 1).Split("&"c)
            vars.Add(i.Substring(0, i.IndexOf("=")), i.Substring(i.IndexOf("=") + 1))
        Next

        getvars = vars
    End Sub

    Public Overrides Function ToString() As String
        Dim request As String = page & "?"

        For Each i As KeyValuePair(Of String, String) In getvars
            request += i.Key + "=" + i.Value & "&"
        Next

        request = request.Substring(0, request.Length - 1)

        Return "GET /pokemondpds/" & request & " HTTP/1.1\r\n" & "Host: gamestats2.gs.nintendowifi.net\r\nUser-Agent: GameSpyHTTP/1.0\r\n" & "Connection: close\r\n\r\n"
    End Function

    Public Function RequestedPage() As String
        Dim RequestedPage1 As String = page & "?"

        For Each i As KeyValuePair(Of String, String) In getvars
            RequestedPage1 += i.Key + "=" + i.Value & "&"
        Next

        Return RequestedPage1.Substring(0, RequestedPage1.Length - 1)
    End Function
End Class

Public Class Response
    Private data As String
    Private headers As New List(Of String)()

    Private p3p As String
    Private server As String
    Private sname As String
    Private cookie As String
    Private len As Integer

    Public Sub New(ByVal h As String)
        If Not h.StartsWith("HTTP/1.1") Then
            data = h
            Return
        End If
        headers.AddRange(h.Split(ControlChars.Lf))

        Dim line As String = Nothing

        Do
            line = headers(0)
            headers.RemoveAt(0)

            If line.StartsWith("P3P") Then
                p3p = line.Substring(line.IndexOf(": ") + 2)
            End If
            If line.StartsWith("cluster-server") Then
                server = line.Substring(line.IndexOf(": ") + 2)
            End If
            If line.StartsWith("X-Server-") Then
                sname = line.Substring(line.IndexOf(": ") + 2)
            End If
            If line.StartsWith("Content-Length") Then
                len = Integer.Parse(line.Substring(line.IndexOf(": ") + 2))
            End If
            If line.StartsWith("Set-Cookie") Then
                cookie = line.Substring(line.IndexOf(": ") + 2)
            End If
        Loop While Not (String.IsNullOrEmpty(line))

        data = String.Join(Environment.NewLine, headers.ToArray())
    End Sub

    Public Overrides Function ToString() As String
        Dim months As String() = {"???", "Jan", "Feb", "Mar", "Apr", "May", _
         "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", _
         "Dec"}
        Dim days As String() = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", _
         "Sun"}

        Dim t As System.DateTime = DateTime.Now



        '"Content-type: application/octet-stream" & vbCrLf & _
        Return (((((((((("HTTP/1.1 200 OK" + Environment.NewLine & "Date: " & days(CInt(t.DayOfWeek)) & ", " & t.Day & " " & t.Month & " " & t.Year & " " & t.Hour & ":" & t.Minute & ":" & t.Second & " GMT") + Environment.NewLine & "Server: Microsoft-IIS/6.0") + Environment.NewLine & "P3P: CP='NOI ADMa OUR STP'") + Environment.NewLine & "cluster-server: aphexweb3") + Environment.NewLine & "X-Server-Name: AW4") + Environment.NewLine & "X-Powered-By: ASP.NET") + Environment.NewLine & "Content-Length: " & data.Length) + Environment.NewLine & "Content-Type: text/html") + Environment.NewLine & "Set-Cookie: ASPSESSIONIDQCDBDDQS=JFDOAMPAGACBDMLNLFBCCNCI; path=/") + Environment.NewLine & "Cache-control: private") + Environment.NewLine + Environment.NewLine & data
    End Function

    Public Function getpkm() As String()
        Dim all As New List(Of String)()
        Dim data_tmp As String = data
        Dim result As String = ""

        While data_tmp.Length > 0
            result = data.Substring(0, 292)
            data = data.Substring(292)
            all.Add(result.Substring(0, 136))
        End While

        Return all.ToArray()
    End Function

End Class
